package com.googlecode.perfectable.visuable.parse;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.google.common.base.Throwables;
import com.googlecode.perfectable.eventable.routing.Sink;
import com.googlecode.perfectable.eventable.routing.Source;
import com.googlecode.perfectable.introspection.Resources;
import com.googlecode.perfectable.prosperable.message.ClientMessage;
import com.googlecode.perfectable.prosperable.message.ServerMessage;
import com.googlecode.perfectable.visuable.configuration.Configurator;
import com.googlecode.perfectable.visuable.visual.Visual;

public class RootFactory {
	private final Configurator configurator;

	private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = createDocumentBuilderFactory();
	private static final EntityResolver localEntityResolver = new LocalEntityResolver();

	public RootFactory(Configurator configurator) {
		this.configurator = configurator;
	}

	public Visual create(InputStream resource, Source<ClientMessage> source, Sink<ServerMessage> sink) {
		DocumentBuilder documentBuilder = createDocumentBuilder();
		Document document;
		try {
			document = documentBuilder.parse(resource);
		}
		catch(SAXException | IOException e) {
			throw Throwables.propagate(e);
		}
		DocumentConverter converter = new DocumentConverter(this.configurator, source, sink);
		Visual root = converter.processDocument(document);
		return root;
	}

	private static DocumentBuilder createDocumentBuilder() {
		DocumentBuilder documentBuilder;
		try {
			documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
			documentBuilder.setEntityResolver(localEntityResolver);
		}
		catch(ParserConfigurationException e) {
			throw Throwables.propagate(e);
		}
		return documentBuilder;
	}

	private static DocumentBuilderFactory createDocumentBuilderFactory() {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			factory.setFeature("http://xml.org/sax/features/namespaces", true);
			factory.setFeature("http://xml.org/sax/features/validation", true);
			factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", true);
			factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true);
		}
		catch(ParserConfigurationException e) {
			throw Throwables.propagate(e);
		}
		return factory;
	}

	private static final class LocalEntityResolver implements EntityResolver {

		public LocalEntityResolver() {
			// prevent syntethic access
		}

		@SuppressWarnings("resource")
		@Override
		public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
				throws SAXException, IOException {
			String path = LocalEntityResolver.calculatePath(systemId);
			InputStream resource = Resources.load(path);
			if(resource == null) {
				throw new IOException("Missing resource " + path + " for systemId " + systemId);
			}
			InputStreamReader reader = new InputStreamReader(resource);
			return new InputSource(reader);
		}

		private static String calculatePath(String systemId) {
			int lastSlash = systemId.lastIndexOf('/'); // MARK no slash
			return "dtd/" + systemId.substring(lastSlash + 1); // MARK constant
		}

	}

}
